package com.chang.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.chang.common.to.SkuEsModel;
import com.chang.search.config.GulimallElasticSearchConfig;
import com.chang.search.constant.EsConstant;
import com.chang.search.service.MallSearchService;
import com.chang.search.vo.SearchParam;
import com.chang.search.vo.SearchResult;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author: changzhenxi
 * @date: 2024年09月23日 15:21
 * @Version: 1.0
 * @description:
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {

    @Resource
    private RestHighLevelClient restClient;

    @Resource
    private HttpServletRequest servletRequest;

    // 从es中获取数据
    @Override
    public SearchResult search(SearchParam searchParam) {
        // 动态构建出查询所需要的dsl语句
        SearchResult result = null;
        // 准备检索请求 直接额外构建一个方法
        SearchRequest searchRequest = buildSearchRequest(searchParam);
        try {
            SearchResponse searchResponse = restClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            result = buildSearchResult(searchResponse, searchParam);

            // 分析响应数据并封装
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    // 构建结果数据
    private SearchResult buildSearchResult(SearchResponse searchResponse, SearchParam searchParam) {
        SearchResult result = new SearchResult();
        List<SkuEsModel> skuEsModelList = new ArrayList<>();

        // 返回所有查询到的商品
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        if (searchHits != null) {
            for (SearchHit hit : searchHits) {
                String sourceAsString = hit.getSourceAsString();
                // 将json数据转为指定的对象
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
                if (searchParam.getKeyword() != null) {
                    // 此处的高亮就是把title添加一层高亮标签
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String highLightSkuTitle = skuTitle.fragments()[0].string();
                    // 包围上标签之后还要替换掉原来的title才能生效
                    skuEsModel.setSkuTitle(highLightSkuTitle);
                }
                skuEsModelList.add(skuEsModel);
            }
        }
        // sku属性是同一类别下所有商品都有的属性,这里获取任意一个商品模型即可获取全部商品属性,如果个别商品没有个别属性则需要在循环里挨个便利封装
        SkuEsModel skuEsModel = skuEsModelList.get(0);
        List<SkuEsModel.Attrs> attrs = skuEsModel.getAttrs();
        // todo 与雷神不同的地方,雷神直接拿属性id远程调用接口获取属性名称,我这边直接利用原有数据获取属性id和属性名称的对应关系,省的从请求参数重获取属性时只有id 还要调用远程方法获取属性名称
        Map<Long, String> attrMap = attrs.stream().collect(Collectors.toMap(SkuEsModel.Attrs::getAttrId, SkuEsModel.Attrs::getAttrName)); //遍历封装,方便下面调用
        // 查询到的所有商品
        result.setProducts(skuEsModelList);

        // 当前所有商品涉及到的所有属性信息
        List<SearchResult.AttrVo> attrVoList = new ArrayList<>();
        ParsedNested attrAgg = searchResponse.getAggregations().get("attr_agg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attr_id_agg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            // 得到属性id
            Long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValueList = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).toList();
            attrVo.setAttrValues(attrValueList);
            attrVoList.add(attrVo);
        }


        result.setAttrs(attrVoList);
        // 当前所有商品涉及的品牌信息
        List<SearchResult.BrandVo> brandVoList = new ArrayList<>();
        ParsedLongTerms brandAgg = searchResponse.getAggregations().get("brand_agg");
        List<? extends Terms.Bucket> brandBuckets = brandAgg.getBuckets();
        for (Terms.Bucket bucket : brandBuckets) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            brandVo.setBrandId(bucket.getKeyAsNumber().longValue());
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg"); // 得到品牌图片的子聚合
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString(); // 品牌图片聚合的第一个字符就是图片地址，顺便转换为String字符串
            brandVo.setBrandImg(brandImg);
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg"); // 同理拿到品牌名称的子聚合
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);
            brandVoList.add(brandVo);
        }
        result.setBrands(brandVoList);

        // 当前所有商品涉及到的所有分类信息 数据结构为分类聚合中包含了两个子聚合
        List<SearchResult.catelogVo> catelogVoList = new ArrayList<>();
        ParsedStringTerms catelogAgg = searchResponse.getAggregations().get("catelog_agg");
        List<? extends Terms.Bucket> buckets = catelogAgg.getBuckets().stream().toList();
        for (Terms.Bucket bucket : buckets) {
            SearchResult.catelogVo catelogVo = new SearchResult.catelogVo();
            String keyAsString = bucket.getKeyAsString();
            catelogVo.setCatelogId(Long.parseLong(keyAsString)); // 得到分类id
            // 获取分类名子聚合
            ParsedStringTerms catelogNameAgg = bucket.getAggregations().get("catelog_name_agg");
            String catelogName = catelogNameAgg.getBuckets().get(0).getKeyAsString();
            catelogVo.setCatelogName(catelogName);
            catelogVoList.add(catelogVo);
        }
        result.setCatelogs(catelogVoList);
        // 分页信息的页码
        result.setPageNum(searchParam.getPageNum());
        // 分页信息总记录数
        long totals = 0;
        if (hits.getTotalHits() != null) {
            totals = hits.getTotalHits().value;
        }
        result.setTotal(totals);
        // 分页信息总页码 总记录数除以每页显示数,非整数再加一页
        int totalPages = totals % EsConstant.PRODUCT_PAGESIZE == 0 ? (int) totals / EsConstant.PRODUCT_PAGESIZE : (int) totals / EsConstant.PRODUCT_PAGESIZE + 1;
        result.setTotalPages(totalPages);

        ArrayList<Integer> pageNavs = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        // 构建面包屑导航功能  此处是通过获取查询参数来记录的，但是这里只记录了属性id和属性值(attrs=15_骁龙)，没有属性名字，所以还得调用远程服务获取
        // 我感觉很鸡肋很臃肿,因为这些需要的字段原本就在前端展示着,应该由前端来做最合适
        if (searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()) {
            String queryString = servletRequest.getQueryString(); // 获取请求字符串
            List<SearchResult.NavVo> navVoList = searchParam.getAttrs().stream().map(attr -> {
                        SearchResult.NavVo navVo = new SearchResult.NavVo();
                        String[] strings = attr.split("_");
                        String attrName = attrMap.get(Long.parseLong(strings[0]));
                        navVo.setNavValue(strings[1]);
                        navVo.setNavName(attrName);
                        // replace无法识别中文,需要将中文转换为UTF_8格式然后再替换
                        String attrEncode = null; // 浏览器对空格店编码和java不一样，需要替换
                        try {
                            attrEncode = URLEncoder.encode(attr, "UTF-8").replace("+", "%20");
                        } catch (UnsupportedEncodingException e) {
                            throw new RuntimeException(e);
                        }
                        // 取消掉面包屑的筛选条件之后,重新获取剩余筛选条件并拼接到url中
                        String newQueryStr = queryString.replace("&attrs=" + attrEncode, "");
                        navVo.setLink("http://search.gulimall.com/list.html?" + newQueryStr);
                        System.out.println("navVo = " + navVo);
                        return navVo;
                    }

            ).toList();
            result.setNavs(navVoList);
        }
        return result;
    }

    // 准备检索请求
    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 构建dsl语句

        // 构建boolQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }
        // 按照三级分类id查询
        if (searchParam.getCatalogId() != null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalogId()));
        }
        // 按照品牌id进行查询
        if (searchParam.getBrandIds() != null && !searchParam.getBrandIds().isEmpty()) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandIds()));
        }
        // TODO 按照所有指定属性查询
        if (searchParam.getAttrs() != null && !searchParam.getAttrs().isEmpty()) {
            BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
            //数据格式类似于->  attr1屏幕属性_8寸:12寸   attr2cpu属性=骁龙888:骁龙777
            for (String attr : searchParam.getAttrs()) {
                String[] attrArray = attr.split("_");
                String attrId = attrArray[0]; // 检索的属性id
                String[] attrValues = attrArray[1].split(":"); // 这个属性检索使用的多个值
                nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue", attrValues));
                // 每一个属性都必须生成一个nested查询
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                boolQuery.filter(nestedQuery);
            }
        }

        // 按照是否拥有库存进行查询 有库存 1 无库存 0
        if (searchParam.getHasStock() != null) {
            boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }

        // 按照价格区间进行检索
        if (StringUtils.isNotEmpty(searchParam.getSkuPrice())) { // 此处传递过来的价格区间数据形式为 1_500/_500/500_ 来表示 D
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] strings = searchParam.getSkuPrice().split("_"); // 以下挂线分割价格区间的最低价格和最高价格 如1_500返回一个数组[1,500]
            if (strings.length == 2) {
                if (StringUtils.isNotBlank(strings[0]) && StringUtils.isNotBlank(strings[1])) {
                    rangeQuery.gte(strings[0]).lte(strings[1]);
                }
                // _500使用split("_")分割也会分割出两个字符,一个是空一个是500 所以此处需要做判断
                if (StringUtils.isBlank(strings[0])) {
                    rangeQuery.lte(strings[1]);
                }
            }
            // 500_使用split("_")分割只会分出一个字符
            if (strings.length == 1) {
                rangeQuery.gte(strings[0]);
            }
            boolQuery.filter(rangeQuery);
        }
        // 把前面所有的条件都拿来进行封装
        sourceBuilder.query(boolQuery);
        // 排序 分页  高亮
        if (StringUtils.isNotBlank(searchParam.getSort())) {
            String[] sortArray = searchParam.getSort().split("_");
            SortOrder order = sortArray[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(sortArray[0], order);
        }
        // from从第几个开始，计算方式为（页码-1）*pageSize
        if (searchParam.getPageNum() != null && searchParam.getPageNum() > 1) {
            sourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
        }
        sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        // 高亮
        if (StringUtils.isNotBlank(searchParam.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            sourceBuilder.highlighter(highlightBuilder);
        }


        // 聚合分析
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg");
        brandAgg.field("brandId").size(50);
        // 品牌聚合的子聚合
        brandAgg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName")).size(1);
        brandAgg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg")).size(1);
        sourceBuilder.aggregation(brandAgg);

        // 分类聚合
        TermsAggregationBuilder catelogAgg = AggregationBuilders.terms("catelog_agg").field("catelogId").size(50);
        catelogAgg.subAggregation(AggregationBuilders.terms("catelog_name_agg").field("catelogName")).size(1);
        catelogAgg.subAggregation(AggregationBuilders.terms("catelog_img_agg").field("catelogImg")).size(1);
        sourceBuilder.aggregation(catelogAgg);

        // 属性聚合
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attr_agg", "attrs");
        // 聚合出当前所有的attr_id
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(50);
        // 聚合分析attr_id对应的名字
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName")).size(1);
        // 聚合分析出当前attr_id对应的所有可能的属性值attr_value
        attrIdAgg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue")).size(50);
        attrAgg.subAggregation(attrIdAgg);
        sourceBuilder.aggregation(attrAgg);

        System.out.println("sourceBuilder.toString() = " + sourceBuilder);
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
        return searchRequest;
    }
}
